﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using Devsoft.Utilities;
using Devsoft.Utilities.Extentions;

namespace Devsoft.DB.Models
{
    public class ProcessBatchErrorAgrs<T>:System.EventArgs
    {
        public List<T> Data { get; set; }
        public Exception Exception { get; set; }
    }

    public class ObjectWriter<T> where T : class
    {
        #region Constructors

        public ObjectWriter(Manager<T> manager)
        {
            _manager = manager;
            BatchSize = 100;
            Interval = 1000;
            TryTimes = 3;
            _items = new List<T>();
        }

        #endregion

        #region Events

        public event EventHandler<ProcessBatchErrorAgrs<T>> Error;


        public event EventHandler<DataEventArgs<List<T>>> BeforeExecuting;

        public event EventHandler<DataEventArgs<List<T>>> AfterExecuting;

        public void OnBeforeExecuting(DataEventArgs<List<T>> e)
        {
            EventHandler<DataEventArgs<List<T>>> handler = BeforeExecuting;
            if (handler != null) handler(this, e);
        }

        public void OnAfterExecuting(DataEventArgs<List<T>> e)
        {
            EventHandler<DataEventArgs<List<T>>> handler = AfterExecuting;
            if (handler != null) handler(this, e);
        }

        public event EventHandler<WriterProgress> ItemsWrote;
        public void OnItemWrote(int i)
        {
            OnItemWrote(new WriterProgress() { NumWritten = i });
        }
        public void OnItemWrote(WriterProgress e)
        {
            EventHandler<WriterProgress> handler = ItemsWrote;
            if (handler != null) handler(this, e);
        }

        #endregion

        public bool UseUpdate { get; set; }

        

        #region Properties

        public int Interval { get; set; }
        public int BatchSize { get; set; }
        public int TryTimes { get; set; }
        public Manager<T>  Manager
        {
            get { return _manager; }
        }
        #endregion

        public void Add(T item)
        {
            lock (_items)
            {
                _count++;
                _items.Add(item);
            }
        }

        public void Enqueue(T reportRow)
        {
            Add(reportRow);
        }

        public void Add(IEnumerable<T> items)
        {
            lock (_items)
            {
                var tmp = items.ToList();
                _count += tmp.Count();
                _items.AddRange(tmp);
            }
        }
        
        public ObjectWriter<T> Start()
        {
            _stop = false;
            _timer = new Timer(Run,null,Interval,Interval);
            return this;
        }

        public void WaitForComplete(int timeOutInSecond = -1)
        {
            var watch = new Stopwatch();
            watch.Start();
            while (_count > 0)
            {
                Thread.Sleep(200);
                if (timeOutInSecond > 1 && watch.Elapsed.TotalSeconds > timeOutInSecond)
                {
                    throw new Exception("Object Writer Timeout");
                }
            }
        }

        private void Run(object state)
        {
            var localList = new List<T>();
            lock (_items)
            {
                localList = new List<T>(_items);
                _items.Clear();
            }
            ProcessBatch(localList);
            if (_stop && localList.Count == 0)
            {
                if (_timer != null)
                {
                    try
                    {
                        _timer.Change(Int32.MaxValue, Int32.MaxValue);
                        _timer.Dispose();
                    }
                    catch (Exception){}
                    finally
                    {
                        _timer = null;
                    }
                }
            }
        }

        

        private void ProcessBatch(List<T> localList)
        {
            if (localList.Count > 0)
            {
                lock (_manager)
                {
                    OnBeforeExecuting(localList.ToEventArgs());
                    try
                    {
                        TaskHelper.Try(() =>
                        {
                            if (UseUpdate)
                            {
                                _manager.Update(BatchSize, localList);
                            }
                            else
                            {
                                _manager.Insert(BatchSize, localList);
                            }
                        }, numTrys: TryTimes);
                    }
                    catch (Exception ex)
                    {
                        //most of errors handled by the Try method. Here we catched some SQL Error like syntax error 
                        //or oversize column field
                        if (Error != null)
                        {
                            Error(this, new ProcessBatchErrorAgrs<T>()
                                {
                                    Data = localList,
                                    Exception = ex
                                });
                        }
                        else throw; //no handler? just throw the exception !
                    }
                    
                    OnAfterExecuting(localList.ToEventArgs());
                    OnItemWrote(localList.Count);
                    _count -= localList.Count;
                }
            }
        }

        public void Stop()
        {
            this.Flush();
            this.WaitForComplete();
            _stop = true;   
        }

        public void Flush()
        {
            lock (_items)
            {
                var localList = new List<T>(_items);
                _items.Clear();
                ProcessBatch(localList);
            }
        }

        private bool _stop;
        private List<T> _items;
        private readonly Manager<T> _manager;
        private System.Threading.Timer _timer;
        //private NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
        private int _count;

        public class WriterProgress:System.EventArgs
        {
            public int NumWritten { get; set; } 
        }

        
    }
}
